﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Sys = System.Timers;

namespace UpgradeLoader
{

    /// <summary>
    /// 1.最多同时下载5个文件
    /// 2、默认30s进行一次心跳访问
    /// 3、断线默认超过10分钟，我们会停止下载，然后保存下载参数到downInfo.json文件
    /// 4、停止下载后，恢复连接后，我们会恢复下载。
    /// </summary>
    public class OneFileDownCenter
    {
        private string heartBeatFile = "heart.xml";//请求心跳用，文件服务器必须要有这个文件

        private string downingJsonFile = "downInfo.json";//下载终止时，各个文件的下载情况保存到此json文件
        private Sys.Timer timer;
        /// <summary>
        /// 心跳断线为false
        /// </summary>
        private bool IsConnect = false;

        /// <summary>
        /// 下载服务器http地址
        /// </summary>
        private string server;

        /// <summary>
        /// 断线时间
        /// </summary>
        private DateTime lostTime;

        private DateTime onlineTime;

        /// <summary>
        /// 断线超时时间分钟
        /// </summary>
        private int downloadTimeoutMins = 10;

        /// <summary>
        /// 是否需要恢复
        /// </summary>
        private bool isNeedRecover = false;


        /// <summary>
        /// 更新配置
        /// </summary>
        ServerUpgradeInfo currentSett;

        /// <summary>
        /// 是否全部下载完成
        /// </summary>
        public bool IsDownFinished = false;

        public string HeartUrl = "http://127.0.0.1:5769/heart.xml";

        public event Action<DownloadItem>? DownloadFinishEventAsync;
        public event Action<long, long, int> DownProgressEvent;
        public event Action<long> DownloadStartEvent;

        public OneFileDownCenter(ServerUpgradeInfo update)
        {
            currentSett = update;
            timer = new Sys.Timer(30 * 1000);
            timer.Elapsed += Timer_Elapsed;
            lostTime = DateTime.Now;
            timer.Start();
        }


        public void StartDownload()
        {

            var downItem = new DownloadItem()
            {
                DownOffset = 0,
                DownUrl = currentSett.UpgradeZipFile,
                IsZip = true,
                DestFileName = currentSett.FileName,
                IsFinish = false,
                IsDownSuccess = false,
                LocalDir = currentSett.SaveDir
            };

            if (File.Exists(downingJsonFile))
            {
                var downInfo = Util.ToObj<DowningFile>(downingJsonFile);


                if (downInfo.Version >= currentSett.CurrentVersionNum && downInfo.Items.Count > 0)
                {
                    downItem = downInfo.Items[0];
                }
            }

            Task.Run(async () =>
            {
                var num = await DownloadFileAsync(downItem);
                if (num == 0)
                {
                    IsDownFinished = true;//全部下载完成
                    if (File.Exists(downingJsonFile))//删除临时json
                    {
                        File.Delete(downingJsonFile);
                    }
                    DownloadFinishEventAsync?.Invoke(downItem);
                }
                else
                {
                    var downloading = new List<DownloadItem>();
                    //中途没有下载完成，保存临时json文件
                    downloading.Add(downItem);
                    var file = new DowningFile() { Version = currentSett.CurrentVersionNum, Items = downloading };
                    var jsonStr = Util.ToJson(file);
                    File.WriteAllText(downingJsonFile, jsonStr);
                }
            });
        }

        public async Task<int> DownloadFileAsync(DownloadItem item)
        {
            string url = item.DownUrl;
            Util.CreateFolder(item.LocalDir);
            string localPath = item.LocalDir + item.DestFileName;
            var offset = (int)item.DownOffset;
            FileMode fileMode = FileMode.Create;
            if (offset > 0 && File.Exists(localPath))
            {
                fileMode = FileMode.Open;
            }
            else
            {
                offset = 0;
                fileMode = FileMode.Create;
            }

            using (HttpClient client = new HttpClient())
            {
                client.Timeout = TimeSpan.FromMinutes(downloadTimeoutMins);
                FileStream fileStream = null;
                try
                {
                    using (HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
                    {
                        if (!response.IsSuccessStatusCode)
                        {
                            return -1;
                        }
                        using (Stream stream = await response.Content.ReadAsStreamAsync())
                        {

                            if (offset > 0)
                            {
                                var bufferOffset = new byte[8192];
                                long readCount = 0;
                                int read = 0;
                                var readLength = Math.Min(bufferOffset.Length, offset);
                                while (readCount < offset)
                                {
                                    readLength = (int)Math.Min(offset - readCount, readLength);

                                    read = await stream.ReadAsync(bufferOffset, 0, readLength);
                                    readCount += read;

                                }
                            }


                            fileStream = new FileStream(localPath, fileMode, FileAccess.Write, FileShare.None, 8192, true);
                            {
                                if (offset > 0)
                                {
                                    fileStream.Seek(offset, SeekOrigin.Begin);
                                }
                                byte[] buffer = new byte[8192];
                                int bytesRead;
                                long totalBytesRead = 0;
                                long totalBytes = response.Content.Headers.ContentLength ?? -1;
                                DownloadStartEvent?.Invoke(totalBytes-offset);
                                while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                                {
                                    await fileStream.WriteAsync(buffer, 0, bytesRead);
                                    totalBytesRead += bytesRead;
                                    item.DownOffset = totalBytesRead;
                                    //Thread.Sleep(2000);
                                    // 处理下载进度，例如更新UI
                                    //   Console.WriteLine($"下载进度：{totalBytesRead}/{totalBytes}");
                                    DownProgressEvent?.Invoke(totalBytes, totalBytesRead, offset);
                                }
                                item.IsFinish = true;
                            }
                        }
                        return 0;
                    }
                }
                catch (TaskCanceledException ex)
                {
                    return -2;
                }
                catch (Exception ex2)
                {
                    return -2;
                }
                finally
                {
                    if (fileStream != null)
                    {
                        fileStream.Close();
                        fileStream.Dispose();
                    }
                }
            }
        }

        private void Timer_Elapsed(object? sender, Sys.ElapsedEventArgs e)
        {
            TickRequst();

            if (IsConnect == false)
            {
                if ((DateTime.Now - lostTime).TotalMinutes > downloadTimeoutMins)
                {
                    isNeedRecover = true;
                    // stopDownload();
                }
            }
            else
            {
                if (isNeedRecover == true)
                {
                    lostTime = DateTime.Now;
                    isNeedRecover = false;
                    if (IsDownFinished == false)
                    {
                        RecoverDownload();

                    }

                }
            }

        }

        /// <summary>
        /// 超过10分钟（downloadTimeoutMins）断线没连上，就会停止下载，然后比如20分钟后又连上了，我们就重新恢复下载
        /// </summary>
        /// <exception cref="Exception"></exception>
        private void RecoverDownload()
        {
            if (!File.Exists(downingJsonFile)) { throw new Exception("未找到下载终止的文件"); }

            var jsonStr = File.ReadAllText(downingJsonFile);
            try
            {
                DowningFile file = JsonSerializer.Deserialize<DowningFile>(jsonStr)!;
                var downingList = file.Items;

                if (downingList.Count > 0)
                {
                    StartDownload();
                }

            }
            catch (Exception ex)
            {

                throw ex;
            }

        }


        private void TickRequst()
        {
            HttpClient client = new HttpClient();
            client.Timeout = TimeSpan.FromSeconds(20);
            HttpRequestMessage requestHeart = new HttpRequestMessage(HttpMethod.Get, HeartUrl);
            try
            {
                var response = client.Send(requestHeart);
                if (response.IsSuccessStatusCode)
                {
                    IsConnect = true;
                    onlineTime = DateTime.Now;
                }
                client.Dispose();
                response.Dispose();
                requestHeart.Dispose();
            }
            catch (TaskCanceledException ex)
            {
                IsConnect = false;
                lostTime = DateTime.Now;
            }
        }

        public bool CheckSumFile(DownloadItem checkItem)
        {
            var rightMd5 = currentSett.MD5;

            string fileName = string.Empty;
            fileName = Path.Combine(checkItem.LocalDir, checkItem.DestFileName);
            if (File.Exists(fileName))
            {
                if (Util.GetMD5HashFromFile(fileName) != rightMd5)
                {
                    checkItem.IsDownSuccess = false;
                }
                else
                {
                    checkItem.IsDownSuccess = true;
                }
            }
            else
            {
                throw new Exception($"文件{fileName}不存在");
            }
            return true;
        }
        internal void RetryDownload()
        {
            if (File.Exists(downingJsonFile))
            {
                File.Delete(downingJsonFile);
            }
                StartDownload();
        }
    }
}
